Skip to content

notify extension dev server of app assets updates#7227

Open
isaacroldan wants to merge 2 commits intomainfrom
04-09-notify_extension_dev_server_of_app_assets_updates
Open

notify extension dev server of app assets updates#7227
isaacroldan wants to merge 2 commits intomainfrom
04-09-notify_extension_dev_server_of_app_assets_updates

Conversation

@isaacroldan
Copy link
Copy Markdown
Contributor

@isaacroldan isaacroldan commented Apr 9, 2026

WHY are these changes introduced?

Adds support for serving app-level assets (such as admin static files) through the development server to enable proper asset handling during local development.

WHAT is this pull request doing?

  • Adds appAssetsConfig interface to extension specifications to define asset configuration
  • Implements appAssetsConfig for admin extensions to handle static_root configuration
  • Creates new HTTP middleware to serve app assets at /extensions/assets/:assetKey/ endpoints
  • Adds asset metadata to the extensions payload including URLs and timestamps
  • Updates file watcher to monitor all real extensions instead of just non-config extensions
  • Changes manifest generation to overwrite existing manifest.json files instead of throwing errors
  • Includes asset timestamp updates when extension events occur
  • Adds workaround to always include admin extension in dev session manifests

How to test your changes?

  1. Create an app with an admin extension that has static_root configured
  2. Add static assets to the configured directory
  3. Run shopify app dev
  4. Verify assets are accessible at the generated asset URLs
  5. Modify assets and confirm timestamps update properly
  6. Test that manifest.json generation works when files already exist

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've considered possible documentation changes
  • I've considered analytics changes to measure impact
  • The change is user-facing, so I've added a changelog entry with pnpm changeset add

Copy link
Copy Markdown
Contributor Author

isaacroldan commented Apr 9, 2026

@isaacroldan isaacroldan force-pushed the 04-09-notify_extension_dev_server_of_app_assets_updates branch from 2670bff to 7fd0331 Compare April 9, 2026 10:25
@isaacroldan isaacroldan force-pushed the 04-08-extract_custom_watch_paths_to_specifications branch 2 times, most recently from 1e0e9ea to 7efd3f7 Compare April 9, 2026 13:12
@isaacroldan isaacroldan force-pushed the 04-09-notify_extension_dev_server_of_app_assets_updates branch 2 times, most recently from 73363b9 to 8e75a20 Compare April 9, 2026 13:24
@isaacroldan isaacroldan force-pushed the 04-08-extract_custom_watch_paths_to_specifications branch from 7efd3f7 to 85f5b0f Compare April 9, 2026 13:24
@isaacroldan isaacroldan force-pushed the 04-09-notify_extension_dev_server_of_app_assets_updates branch from 8e75a20 to 52ef686 Compare April 9, 2026 13:26
@isaacroldan isaacroldan force-pushed the 04-08-extract_custom_watch_paths_to_specifications branch from 85f5b0f to 4c6daca Compare April 9, 2026 13:26
@isaacroldan isaacroldan force-pushed the 04-09-notify_extension_dev_server_of_app_assets_updates branch from 52ef686 to 9cd7adc Compare April 9, 2026 13:29
@isaacroldan isaacroldan force-pushed the 04-08-extract_custom_watch_paths_to_specifications branch from 4c6daca to 1ba2dd9 Compare April 9, 2026 13:29
@isaacroldan isaacroldan force-pushed the 04-09-notify_extension_dev_server_of_app_assets_updates branch from 9cd7adc to dc2a701 Compare April 9, 2026 13:35
@isaacroldan isaacroldan force-pushed the 04-08-extract_custom_watch_paths_to_specifications branch from 1ba2dd9 to 5dfacdb Compare April 9, 2026 13:35
@isaacroldan isaacroldan force-pushed the 04-09-notify_extension_dev_server_of_app_assets_updates branch from dc2a701 to c6ca3d1 Compare April 9, 2026 13:38
@isaacroldan isaacroldan force-pushed the 04-08-extract_custom_watch_paths_to_specifications branch from 82d0865 to 8c27933 Compare April 9, 2026 14:13
@isaacroldan isaacroldan force-pushed the 04-09-notify_extension_dev_server_of_app_assets_updates branch 2 times, most recently from 665154d to a1b4321 Compare April 9, 2026 14:19
@isaacroldan isaacroldan force-pushed the 04-08-extract_custom_watch_paths_to_specifications branch from 8c27933 to fc760cc Compare April 9, 2026 14:19
@isaacroldan isaacroldan force-pushed the 04-09-notify_extension_dev_server_of_app_assets_updates branch from a1b4321 to fa4c046 Compare April 9, 2026 14:28
@isaacroldan isaacroldan force-pushed the 04-08-extract_custom_watch_paths_to_specifications branch 2 times, most recently from 3383e69 to d1271b9 Compare April 9, 2026 14:32
@isaacroldan isaacroldan force-pushed the 04-09-notify_extension_dev_server_of_app_assets_updates branch 5 times, most recently from b655072 to 3dc00e6 Compare April 9, 2026 16:04
@elanalynn elanalynn mentioned this pull request Apr 9, 2026
4 tasks
@github-actions
Copy link
Copy Markdown
Contributor

Differences in type declarations

We detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:

  • Some seemingly private modules might be re-exported through public modules.
  • If the branch is behind main you might see odd diffs, rebase main into this branch.

New type declarations

We found no new type declarations in this PR

Existing type declarations

packages/cli-kit/dist/private/node/ui.d.ts
 import { Logger, LogLevel } from '../../public/node/output.js';
-import React from 'react';
 import { Key, RenderOptions } from 'ink';
 import { EventEmitter } from 'events';
-/**
- * Signal that the current Ink tree is done. Must be called within an
- * InkLifecycleRoot — throws if the provider is missing so lifecycle
- * bugs surface immediately instead of silently hanging.
- */
-export declare function useComplete(): (error?: Error) => void;
-/**
- * Root wrapper for Ink trees. Owns the single `exit()` call site — children
- * signal completion via `useComplete()`, which sets state here. The `useEffect`
- * fires post-render, guaranteeing all batched state updates have been flushed
- * before the tree is torn down.
- */
-export declare function InkLifecycleRoot({ children }: {
-    children: React.ReactNode;
-}): React.JSX.Element;
 interface RenderOnceOptions {
     logLevel?: LogLevel;
     logger?: Logger;
     renderOptions?: RenderOptions;
 }
 export declare function renderOnce(element: JSX.Element, { logLevel, renderOptions }: RenderOnceOptions): string | undefined;
-export declare function render(element: JSX.Element, options?: RenderOptions): Promise<void>;
+export declare function render(element: JSX.Element, options?: RenderOptions): Promise<unknown>;
 export declare class Stdout extends EventEmitter {
     columns: number;
     rows: number;
     readonly frames: string[];
     private _lastFrame?;
     constructor(options: {
         columns?: number;
         rows?: number;
     });
     write: (frame: string) => void;
     lastFrame: () => string | undefined;
 }
 export declare function handleCtrlC(input: string, key: Key, exit?: () => void): void;
 export {};
packages/cli-kit/dist/public/node/ui.d.ts
@@ -34,7 +34,7 @@ export interface RenderConcurrentOptions extends PartialBy<ConcurrentOutputProps
  * 00:00:00 │ frontend │ third frontend message
  *
  */
-export declare function renderConcurrent({ renderOptions, ...props }: RenderConcurrentOptions): Promise<void>;
+export declare function renderConcurrent({ renderOptions, ...props }: RenderConcurrentOptions): Promise<unknown>;
 export type AlertCustomSection = CustomSection;
 export type RenderAlertOptions = Omit<AlertOptions, 'type'>;
 /**

@isaacroldan isaacroldan force-pushed the 04-09-notify_extension_dev_server_of_app_assets_updates branch from 535fde3 to 5399272 Compare April 10, 2026 15:40
@isaacroldan isaacroldan force-pushed the 04-08-extract_custom_watch_paths_to_specifications branch from d1271b9 to 06b34f4 Compare April 10, 2026 15:40
@isaacroldan isaacroldan marked this pull request as ready for review April 10, 2026 15:42
@isaacroldan isaacroldan requested a review from a team as a code owner April 10, 2026 15:42
Base automatically changed from 04-08-extract_custom_watch_paths_to_specifications to main April 10, 2026 16:59
@isaacroldan isaacroldan force-pushed the 04-09-notify_extension_dev_server_of_app_assets_updates branch from 5399272 to 42083ba Compare April 10, 2026 17:14
Copilot AI review requested due to automatic review settings April 13, 2026 15:57
@isaacroldan isaacroldan force-pushed the 04-09-notify_extension_dev_server_of_app_assets_updates branch from 42083ba to c77befd Compare April 13, 2026 15:57
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the local UI extensions development server to support serving app-level assets (notably admin static_root) and to propagate related metadata (URLs + timestamps) through the extensions payload so clients can fetch and refresh those assets during development.

Changes:

  • Adds app.allowed_domains and app.assets metadata into the extensions payload and shared type definitions.
  • Introduces an HTTP middleware + route (/extensions/assets/:assetKey/...) for serving app assets from configured directories.
  • Expands dev file watching and build/include-assets behavior (watch realExtensions; overwrite generated manifest.json; admin spec updates).

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/ui-extensions-server-kit/src/types.ts Extends App payload types with allowed_domains and assets.
packages/app/src/cli/services/dev/processes/setup-dev-processes.ts Passes realExtensions into previewable extension process setup.
packages/app/src/cli/services/dev/processes/dev-session/dev-session.ts Workaround to always include admin in update manifests.
packages/app/src/cli/services/dev/extension/server/middlewares.ts Adds middleware to serve app assets by key/path.
packages/app/src/cli/services/dev/extension/server.ts Registers the new app-assets route when asset directories exist.
packages/app/src/cli/services/dev/extension/payload/store.ts Adds admin-derived app config/assets to payload and tracks asset directories/timestamps.
packages/app/src/cli/services/dev/extension/payload/store.test.ts Adjusts store option mocks for new constructor usage.
packages/app/src/cli/services/dev/extension/payload/models.ts Updates payload interface with new app fields.
packages/app/src/cli/services/dev/extension.ts Triggers admin config/timestamp updates on extension events; refactors server setup call.
packages/app/src/cli/services/dev/extension.test.ts Updates mocks/assertions for new payload store methods.
packages/app/src/cli/services/dev/app-events/file-watcher.ts Watches realExtensions files (including config extensions).
packages/app/src/cli/services/build/steps/include-assets/generate-manifest.ts Changes manifest generation to overwrite existing manifest.json.
packages/app/src/cli/services/build/steps/include-assets-step.test.ts Updates tests to expect manifest overwrite behavior.
packages/app/src/cli/models/extensions/specifications/admin.ts Adds allowed_domains to schema; exports AdminConfigType; watches static_root files.
packages/app/src/cli/models/extensions/specification.ts No functional change (formatting).
packages/app/src/cli/models/extensions/extension-instance.ts Uses isAppConfigExtension when deciding default dev-session watch behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +137 to +147
export function getAppAssetsMiddleware(getAppAssets: () => Record<string, string> | undefined) {
return defineEventHandler(async (event) => {
const {assetKey = '', filePath = ''} = getRouterParams(event)
const appAssets = getAppAssets()
const directory = appAssets?.[assetKey]
if (!directory) {
return sendError(event, {statusCode: 404, statusMessage: `No app assets configured for key: ${assetKey}`})
}
return fileServerMiddleware(event, {
filePath: joinPath(directory, filePath),
})
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filePath comes from the URL and is joined directly with the configured directory. Because joinPath() will allow absolute segments and .., a request like /extensions/assets/staticRoot/../../../../etc/passwd (or a leading /) can escape the intended directory and expose arbitrary files. Resolve the requested path and enforce it stays within the configured directory (e.g., resolvePath(directory, filePath) + isSubpath(directory, resolved)), and reject absolute paths / traversal with a 400/403.

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +55
// Admin extension contributes app-level config to the payload
const adminExtension = options.extensions.find((ext) => ext.type === 'admin')
if (adminExtension) {
const adminConfig = (adminExtension.configuration as AdminConfigType).admin
if (adminConfig?.allowed_domains) {
payload.app.allowed_domains = adminConfig.allowed_domains
}
if (adminConfig?.static_root) {
const assetKey = 'staticRoot'
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic derives adminExtension from options.extensions, but in the previewable extensions dev server options.extensions is filtered to ext.isPreviewable (so admin is excluded). That means payload.app.allowed_domains / payload.app.assets will never be set even when an admin extension exists. Consider sourcing the admin extension from the full app extension list (or pass it separately) rather than from the previewable-only list.

Copilot uses AI. Check for mistakes.
Comment on lines 150 to 156
private getAllWatchedFiles(): string[] {
this.extensionWatchedFiles.clear()

const extensionResults = this.app.nonConfigExtensions.map((extension) => ({
const extensionResults = this.app.realExtensions.map((extension) => ({
extension,
watchedFiles: extension.watchedFiles(),
}))
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getAllWatchedFiles() now includes realExtensions, but extensionPaths (used to decide whether to emit delayed file_deleted events) is still initialized from nonConfigExtensions in updateApp(). For configuration extensions like admin, this causes delete events to be dropped (extensionPaths.includes(extensionPath) is false), so asset deletions won’t trigger extension events/timestamp updates. Consider tracking extensionPaths for realExtensions as well to keep event emission consistent with the new watch set.

Copilot uses AI. Check for mistakes.
Comment on lines +354 to +358
// WORKAROUND. This is a temporary fix because `admin` is not compatible with inheritedUids in Core.
// It needs to be included in the manifest always if present in the app.
if (appEvent.app.allExtensions.some((ext) => ext.type === 'admin')) {
updatedUids.push('admin')
}
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This workaround unconditionally pushes 'admin' into updatedUids whenever an admin extension exists. Since updatedUids is later used to filter the manifest modules for UPDATE sessions, this will force the admin module into every update (potentially increasing bundle size/upload time) and may add duplicates. At minimum, de-dupe updatedUids (e.g., use a Set) and add a tracking reference (issue/PR) so the workaround can be removed once Core supports inheritedUids for admin.

Copilot uses AI. Check for mistakes.
@isaacroldan isaacroldan force-pushed the 04-09-notify_extension_dev_server_of_app_assets_updates branch 3 times, most recently from 9dd6439 to 8cb333a Compare April 13, 2026 16:43
@isaacroldan isaacroldan force-pushed the 04-09-notify_extension_dev_server_of_app_assets_updates branch from 8cb333a to 06b860d Compare April 13, 2026 18:28
url: string
mobileUrl: string
title: string
allowed_domains?: string[]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this allowedDomains so it's consistent with the other fields which are camel cased?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants